﻿#pragma once

#include <math.h>
#include <QString>
#include <QImage>
// Three floats that corresponds to X, Y, and Z axis positions in 3D space
typedef struct tagVertex { float X,Y,Z; } Vertex, *LPVERTEX;

// A face definition made of the following unsigned short items:
//	Vertex A, the first vertex index.
//	Vertex B, the second vertex index.
//	Vertex C, the third vertex index.
//	Edge visibility flags where 
//		bit 0 (0x1) sets the CA edge visible, 
//		bit 1 (0x2) sets the BC edge visible,
//		bit 2 (0x4) sets the AB edge visible, 
//		bit 3 (0x8) indicates a U axis texture wrap,
//		bit 4 (0x10) indicates a V axis texture wrap.
typedef struct tagFace  { unsigned short A,B,C,E; } Face, *LPFACE;

// A pair of floats that correspond to the UV coordinates used in texture mapping
typedef struct tagTexVert { float U,V; } TexVert, *LPTEXVERT;

// An integer definition of color (from 0 to 255) 
typedef struct tagColor24 { unsigned char R, G, B; } Color24, *LPCOLOR24;

class CMaterial; // Material definition (MAT_ENTRY 0xAFFF)
typedef CMaterial* LPMATERIAL;
class CMaterial
{
public:
    CMaterial(void) { pnext = NULL; name[0] = mapn[0] = '\0'; pamb = pdif = pspc = NULL; /*pdata = NULL;*/ }
	~CMaterial(void) { Release(); }
	LPMATERIAL	pnext;
public:
	char					name[17];	// Name of material
	char					mapn[13];	// Bitmap filename
    QImage  img; // Bitmap data
	LPCOLOR24	pamb;		// Ambient material color
	LPCOLOR24	pdif;			// Diffuse material color
	LPCOLOR24	pspc;		// Specular material color
public:
	void AddNext(LPMATERIAL pNext) {
		if (pnext) { LPMATERIAL plist = pnext; 	while (plist->pnext) plist = plist->pnext; plist->pnext = pNext; }
		else pnext = pNext;
	}
    void LoadTexture(QString Path);
	void Release(void) {
//        if (pdata)	{ delete[] pdata; pdata = NULL; }
        if (pamb)	{ delete pamb; pamb = NULL; }
        if (pdif)		{ delete pdif; pdif = NULL; }
        if (pspc)	{ delete pspc; pspc = NULL; }
        if (pnext)	{ pnext->Release(); delete pnext;  pnext = NULL; }
	}
//	void VlogMaterial(void);
};

class CMshMat; // A list of faces that are assigned the material named (MSH_MAT_GROUP 0x4130)
typedef CMshMat* LPMSHMAT;
class CMshMat 
{ 
public:
    CMshMat(void)  { pnext = NULL; pface = NULL; }
	~CMshMat(void) { Release(); }
public:
	char					name[17];	// Name of material (1–16 characters plus NULL)
	unsigned short	nface;		// Number of faces assigned material
	unsigned short	*pface;		// A list of face indexes that are assigned the material
	LPMSHMAT	pnext;
public:
	void AddNext(LPMSHMAT pNext) {
		if(pnext) { LPMSHMAT plist = pnext; while(plist->pnext) plist = plist->pnext; plist->pnext = pNext; }
		else pnext = pNext;
	}
	void Release(void) { 
        if(pface) { delete [] pface; pface = NULL; }
        if(pnext) { pnext->Release(); delete pnext; pnext = NULL; }
	}
};

class CMesh;
typedef CMesh* LPMESH;
class CMesh
{
public:
    CMesh(void) { pnext = NULL; pvert = pnormal = NULL; ptvert = NULL; nvert = nface = 0;
                            pface = NULL; pmshmat = NULL; name[0] = '\0'; pmatrix = NULL; }
	~CMesh(void)  { Release(); }
	LPMESH	pnext;
public:
	char					name[11];	// Name of mesh  (NAMED_OBJECT 0x4000 1 to 10 characters plus NULL)
	unsigned short	nvert;		// Количество вершин (the number of texture coordinates must be the number as vertices)
	unsigned short	nface;		// Количество треугольников 
    LPVERTEX	pvert;		// Массив вершин (x,y,z)
    LPVERTEX	pnormal;	// Массив векторов нормалей (x,y,z) к элементам поверхности (nface)
	LPFACE			pface;		// Описание номеров вершин треугольников (1,2,3,e)
	LPTEXVERT	ptvert;		// UV coordinates for texture mapping(u,v)
	LPMSHMAT	pmshmat;	// A list of faces that are assigned the material named
	float*				pmatrix;	// 3-by-4 matrix describing object orientation
public:
	void AddNext(LPMESH pNext) {
		if(pnext) { LPMESH plist = pnext; while(plist->pnext) plist = plist->pnext; plist->pnext = pNext; }
		else pnext = pNext;
	}
	void Release(void) { 
        if(pface)		{ delete [] pface;   pface   = NULL; }
        if(pvert)		{ delete [] pvert;   pvert   = NULL; }
        if(pnormal)	{ delete [] pnormal; pnormal = NULL; }
        if(ptvert)		{ delete [] ptvert;  ptvert  = NULL; }
        if(pmatrix)		{ delete [] pmatrix; pmatrix = NULL; }
        if(pmshmat)	{ pmshmat->Release(); delete pmshmat; pmshmat = NULL; }
        if(pnext)		{ pnext->Release(); delete pnext; pnext = NULL; }
	}
//	void VlogMesh(void);
	bool GetBoundBox(LPVERTEX pbound) {
        if(pvert == NULL) return false;
        if (strncmp(name, "KARKAS\0", strlen(name)) == 0)	return false;
		pbound[0].X = pbound[1].X = pvert[0].X;
		pbound[0].Y = pbound[1].Y = pvert[0].Y;
		pbound[0].Z = pbound[1].Z = pvert[0].Z;
		for(unsigned short i = 1; i < nvert; i++) {
			if(pvert[i].X < pbound[0].X) pbound[0].X = pvert[i].X;
			if(pvert[i].X > pbound[1].X) pbound[1].X = pvert[i].X;
			if(pvert[i].Y < pbound[0].Y) pbound[0].Y = pvert[i].Y;
			if(pvert[i].Y > pbound[1].Y) pbound[1].Y = pvert[i].Y;
			if(pvert[i].Z < pbound[0].Z) pbound[0].Z = pvert[i].Z;
			if(pvert[i].Z > pbound[1].Z) pbound[1].Z = pvert[i].Z;
		}
		return true;
	}

	void SetNormal(void) {
		if(!pnormal) pnormal = new Vertex[nface];
		if(!pnormal) return;
		Vertex d1,d2;
		float d;
		for(unsigned short i = 0; i < nface; i++) {
			d1.X = pvert[pface[i].A].X - pvert[pface[i].B].X;
			d1.Y = pvert[pface[i].A].Y - pvert[pface[i].B].Y;
			d1.Z = pvert[pface[i].A].Z - pvert[pface[i].B].Z;
			d2.X = pvert[pface[i].B].X - pvert[pface[i].C].X;
			d2.Y = pvert[pface[i].B].Y - pvert[pface[i].C].Y;
			d2.Z = pvert[pface[i].B].Z - pvert[pface[i].C].Z;
			pnormal[i].X = d1.Y*d2.Z - d1.Z*d2.Y; 
			pnormal[i].Y = d1.Z*d2.X - d1.X*d2.Z; 
			pnormal[i].Z = d1.X*d2.Y - d1.Y*d2.X; 
			d = sqrt(pnormal[i].X*pnormal[i].X + pnormal[i].Y*pnormal[i].Y + pnormal[i].Z*pnormal[i].Z);
			if(d > 0.f) { pnormal[i].X /= d; pnormal[i].Y /= d; pnormal[i].Z /= d; }
		}
	}
};
struct CameraPos
{
   float pos[3];  // x,y,z (откуда смотрит камера)
   float target[3]; // x,y,z (куда смотрит камера)
   float rot; // camera rotation (градус)
   float focal; // camera focal length (мм)
};
class CFile3ds
{
public:
    CFile3ds(void) {  pMesh = NULL; pMat = NULL; pbound = NULL; }
	~CFile3ds(void) { Release(); }
	//protected:
	LPMESH		pMesh;
	LPMATERIAL	pMat;
	LPVERTEX	pbound;
    CameraPos cam;
public:
//    bool Load3ds(char* Path);
    bool Load3ds(QString FileName);
    void Release(void) {
        if (pbound) { delete[] pbound;  pbound = NULL; }
        if (pMesh)  { pMesh->Release();  pMesh = NULL; }
        if (pMat)   { pMat->Release();   pMat = NULL; }
	}
	bool GetCenterBoundBox(LPVERTEX pc, double &r) { // Определить центр и радиус сферы
        if (pbound == NULL) return false;
		pc->X = (pbound[0].X + pbound[1].X) / 2.f;
		pc->Y = (pbound[0].Y + pbound[1].Y) / 2.f;
		pc->Z = (pbound[0].Z + pbound[1].Z) / 2.f;
		r = sqrt((pbound[1].X - pc->X)*(pbound[1].X - pc->X) + (pbound[1].Y - pc->Y)*(pbound[1].Y - pc->Y) + (pbound[1].Z - pc->Z)*(pbound[1].Z - pc->Z));
		return true;
	}
    int GetXsize() { if (pbound == NULL) return 0; return pbound[1].X - pbound[0].X; }
    int GetYsize() { if (pbound == NULL) return 0; return pbound[1].Y - pbound[0].Y; }
    bool GetAspectRatio(int width, int height, double &dx, double &dy) // Определить aspect ratio
	{
        if (pbound == NULL) return false;
        dx = (pbound[1].X - pbound[0].X) / 4.; // 2.
        dy = (pbound[1].Y - pbound[0].Y) / 4.;
		if (width > height) {
			dx = dy*((double)width / (double)height);
			dy = dx;
		}
		else {
			dy = dx*((double)height / (double)width);
			dx = dy;
		}
		if (width > height) dy /= ((double)width / (double)height);
		else	  dx /= ((double)height / (double)width);

		return true;
	}
	bool SetBoundBox(void);
//	void VlogReport(void); // Лог файл инфо
    LPMESH GetMesh(char* name) {
        if (pMesh == NULL) return NULL;
        LPMESH plist = pMesh;
		while (plist) {
			if (strncmp(plist->name, name, strlen(name)) == 0) {
				if (strlen(plist->name) == strlen(name))
					return plist;
			}
			plist = plist->pnext;
		}
        return NULL;
	}
    LPMATERIAL GetMaterial(char* name) {
        if (pMat == NULL) return NULL;
        LPMATERIAL plist = pMat;
        while (plist) {
            if (strncmp(plist->name, name, strlen(name)) == 0) {
                if (strlen(plist->name) == strlen(name))
                    return plist;
            }
            plist = plist->pnext;
        }
        return NULL;
    }
    void SetNormal(void) {
        if (pMesh == NULL) return;
		LPMESH plist = pMesh;
		while (plist) {
			plist->SetNormal();
			plist = plist->pnext;
		}
	}
    void LoadTextures(QString Path);
};

class CMeshPos; // Класс экранной позиции mesh

typedef CMeshPos* LPMESHPOS;
class CMeshPos 
{ 
public:
    CMeshPos(void)  { pnext = NULL; pmesh = NULL; pmat = NULL; Id = 0;
                      bScreen = false; pos[0] = pos[1] =  pos[2] = pos[3] = 0; dx = 96; dy = 16; } // dx = 0; dy = -64;
  ~CMeshPos(void) { Release(); }
public:
    LPMESH pmesh;	// Point of mesh
    LPMATERIAL pmat; // Point of material
    QImage    ind;
    int			pos[4];		// Position of mesh in window coordinates
    int			dx,dy;	// Position of image in window coordinates (left top corner)
    int			Id;		// Идентификатор (default 0)
    bool        bScreen;   // Признак отображения прибора
    LPMESHPOS	pnext;
public:
    void AddNext(LPMESH pm, int id = 0, LPMATERIAL mat = nullptr) {
        if(pmesh == NULL) {
            pmesh = pm;
            pmat = mat;
			Id = id;
			return;
		}
        LPMESHPOS pNext = new CMeshPos;
        pNext->pmesh = pm;
        pNext->pmat = mat;
        pNext->Id = id;

		if(pnext) {
		    LPMESHPOS plist = pnext;
			while(plist->pnext) plist = plist->pnext;
			plist->pnext = pNext;
		}
		else pnext = pNext;
	}
	LPMESHPOS Find(int id) {
		LPMESHPOS plist = this;
		while(plist) {
			if(plist->Id == id) return plist;
			plist = plist->pnext;
		}
        return NULL;
	}
	void Release(void) { 
        if(pnext) { pnext->Release(); delete pnext; pnext = NULL; }
	}
//    bool SetMaterial(int id, LPMATERIAL mat)
//    {
//        LPMESHPOS mp = Find(id);
//        if(mp == NULL) return false;
//        mp->pmat = mat;
//        return true;
//    }
    bool SetImage(int id, QImage img)
    {
        LPMESHPOS mp = Find(id);
        if(mp == NULL) return false;
        mp->ind = img;
        return true;
    }
    LPMESHPOS DragSelect(int ptCursorx, int ptCursory) { // Определяет положение точки (Алгоритм Кохена-Сазерленда)
        int i = 1;
        int hs = 256, ws = 256;
        if(bScreen) {
            if(Id < 100) { ws  = 640; hs = 480; }
            i = 0;
            int w, h; w = ws + dx; h = hs + dy;
            if ((h - ptCursory) < 0) i = 8;  // точка выше окна
            if ((ptCursory - dy) < 0) i = 4;  // точка ниже окна
            if ((w - ptCursorx) < 0) i += 2; // точка правее окна
            if ((ptCursorx - dx) < 0) i += 1; // точка левее окна
        }
        if(i == 0) 	return this;
        else
        if(pnext) return pnext->DragSelect(ptCursorx, ptCursory);
        return nullptr;
    }
    LPMESHPOS Select(int ptCursorx, int ptCursory, bool bleft) { // Определяет положение точки (Алгоритм Кохена-Сазерленда)
        int i = -1;
        int hs = 256, ws = 256;
        i = 0;
        if((!bleft) && bScreen) { // Нажата не левая кнопка мыши
            if(Id < 100) { ws  = 640; hs = 480; }
            int w, h; w = ws + dx; h = hs + dy;
            if ((h - ptCursory) < 0) i = 8;  // точка выше окна
            if ((ptCursory - dy) < 0) i = 4;  // точка ниже окна
            if ((w - ptCursorx) < 0) i += 2; // точка правее окна
            if ((ptCursorx - dx) < 0) i += 1; // точка левее окна
        }
        else {
            if ((pos[3] - ptCursory) < 0) i = 8;  // точка выше окна
            if ((ptCursory - pos[1]) < 0) i = 4;  // точка ниже окна
            if ((pos[2] - ptCursorx) < 0) i += 2; // точка правее окна
            if ((ptCursorx - pos[0]) < 0) i += 1; // точка левее окна
        }
        if(i == 0) {  bScreen = bleft;	return this; }
		else 
        if(pnext) return pnext->Select(ptCursorx, ptCursory, bleft);
        return nullptr;
	}
};

